Exercice 1 - Module Java
Un module Java consiste en un ensemble de packages dont les propriétés sont bien définies, en particulier :
- un nom
- une liste de ses dépendances
- un point d'accès (API) public
- une liste des services offerts
Au cours de cet exercice, vous allez commencer par lire une déclaration de module réalisée lors d'un TD précédent afin de comprendre exactement son effet sur le module créé,
Déclaration de module
Reprenez l'application HelloApplication que vous avez créée au TD02. Dans cette applications, vous aviez ajouté un fichier module-info.java :
module be.esi.prj {
requires javafx.controls;
requires javafx.fxml;
opens be.esi.prj to javafx.fxml;
exports be.esi.prj;
}
Ce fichier déclare que vos packages forment un module, et définit les propriétés de celui-ci :
- Ligne 1 : Le module se nomme
be.esi.prj - Lignes 2 et 3 : Ses dépendances sont
javafx.controlsetjavafx.fxml. Les types exportés par ces modules sont donc accessibles à notre module à l'exécution. - Ligne 5 : On ouvre l'accès à toutes les classes du package
be.esi.prj(y compris les classes non publiques) au modulejavafx.fxmlvia réflexion. - Ligne 6 : Le package
be.esi.prjest exporté par notre module : il s'agit du point d'accès à notre module. Les classes publiques de ce package sont donc accessibles à l'extérieur du module, alors que les autres packages sont fortement encapsulés et donc inaccessibles.
Export et réflexion
Par défaut, tous les types (classes, interfaces) définis dans un module sont inaccessibles en-dehors de ce module. Il faudra déclarer explicitement quels types nous souhaitons rendre accessibles.
Nous avons vu qu'il y a deux façons différentes de donner accès à notre package be.esi.prj : par réflexion via open, et par export via export.
En Java, la réflexion fait référence à la pratique de consulter, à l'exécution, des informations concernant les classes, interfaces, attributs et méthodes de nos objets. Cette pratique est essentielle, par exemple, lorsque ces informations ne sont pas déterminées à la compilation.
La réflexion est utilisée par de nombreux frameworks, tels que JUnit ou Spring, quand ceux-ci ont besoin par exemple d'instancier des objets de façon dynamique. FXML, par exemple, se sert de la réflexion pour identifier la classe de contrôleur associé à un fichier FXML, et les attributs et méthodes de ce contrôleur annotés via @FXML.
openouvre un package à la réflexion. Toutes les classes du package, quelle que soit leur visibilité, sont alors accessibles.exportsdéfinit un package comme point d'accès. Seules les classes publiques du package sont rendues accessibles à la fois à la compilation et à l'exécution. Ces classes peuvent donc être importées par les classes présentes dans d'autres modules.
Par défaut, un package ouvert ou exporté est accessible à tout module. Il est également possible de qualifier cet accès via le mot-clé to suivi d'un nom de module, afin de ne donner accès qu'à ce module. Cela nous permet d'avoir un contrôle complet sur l'encapsulation de notre module.
De même manière qu'on s'efforce de rendre les attributs privés, et les types et méthodes en visibilité package si possible, une bonne pratique est de limiter le nombre de points d'accès le plus possible.
Dépendance
Une dépendance permet d'accéder, à la compilation (via import) et à l'exécution, aux classes exportées par le module renseigné.
Il est également possible de préciser deux types de dépendances particulières :
requires staticindique une dépendance optionnelle à l'exécution : elle est nécessaire lors de la compilation mais pas à l'exécution. Ceci pourrait survenir dans un cas où la présence de la dépendance permet d'altérer le comportement du module (par exemple pour appeler un code plus efficace), mais si elle n'est pas présente, le code est capable de fonctionner sans ce code.requires transitiveindique que les dépendances de la dépendance sont aussi des dépendances de notre module. Cela permet d'éviter de devoir re-préciser ces dépendances via desrequiressupplémentaires. C'est notamment pertinent lorsque notre dépendance renvoie des résultats d'un type défini dans une de ces dépendances. Par contre, c'est à éviter si nous avons besoin de ce deuxième module pour des comportements distincts de la dépendance déjà renseignée.
Compiler un module
module-info.java est un fichier Java, même s'il ne définit pas une classe.
Consultez le target de votre projet. Dans le répertoire classes, vous y trouverez un fichier module-info.class, version compilée de module-info.java. IntelliJ est d'ailleurs capable de le décompiler comme tout autre fichier .class. Si vous compilez votre projet pour en faire une archive Java (.jar), le fichier module-info.class sera donc inclus, et l'archive contient donc comme un module. Cela veut aussi dire que chaque archive JAR ne contient au plus qu'un seul module.